package com.tegistuff.webos.run.debugger;

import com.intellij.execution.ExecutionException;
import com.palm.webos.device.Device;
import com.palm.webos.device.DeviceConnection;
import com.palm.webos.device.DeviceManager;
import com.palm.webos.tools.debugger.DebugManager;
import com.palm.webos.tools.debugger.V8DebugStream;
import com.palm.webos.tools.fileio.JSONInputStream;
import com.palm.webos.tools.internal.debugger.AppDebugSession;
import com.palm.webos.tools.servicemanager.ServiceManager;
import com.tegistuff.webos.run.RunTarget;
import com.tegistuff.webos.run.WebOSRunConfiguration;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;

/**
 * WebOSDebugSessionProvider.java
 *
 * This helper class is used to establish a connection to a V8 debugger.
 *
 * Copyright (C) 2011-2012 Timo Tegtmeier.
 * All rights reserved.
 */
public class WebOSDebugSessionProvider {

    private String myAppId;
    private int myDebugPort;
    private WebOSRunConfiguration myRunConfiguration;
    private DeviceManager myDeviceManager;
    private Device myDevice;
    private DeviceConnection myDeviceConnection;
    private DebugManager myDebugManager;
    private V8DebugStream myDebugStream;
    private AppDebugSession myDebugSession;

    public WebOSDebugSessionProvider(WebOSRunConfiguration configuration, String appId, int port) {
        myRunConfiguration = configuration;
        myAppId = appId;
        myDebugPort = port;
    }

    /***
     * Connect to the V8 debugger.
     * @return AppDebugSession
     * @throws ExecutionException
     * @throws IOException
     * @throws JSONException
     */
    public AppDebugSession connect() throws ExecutionException, IOException, JSONException {
        if(myDebugSession != null)
            return myDebugSession;

        myDeviceManager = new DeviceManager();
        myDevice = myDeviceManager.getFirstMatch(getTargetDeviceName(myRunConfiguration.getTarget()));
        myDeviceConnection = myDevice.connect();

        enableDebuggerAccess();
        
        myDebugManager = new DebugManager(myDeviceConnection);
        myDebugStream = myDebugManager.debug(myDebugPort);
        myDebugSession = new AppDebugSession(myDebugStream, myAppId);

        myDebugSession.start();

        return myDebugSession;
    }

    /***
     * Disconnect from the V8 debugger.
     */
    public void disconnect() {
        if(myDebugSession == null)
            return;

        closeDebugSession();
        closeDebugStream();
        myDebugManager = null;

        if(myDeviceConnection != null) {
            disableDebuggerAccess();
            myDeviceConnection = null;
        }

        myDevice = null;
        myDeviceManager = null;
    }

    /***
     * Close the debug session.
     */
    private void closeDebugSession() {
        if(myDebugSession != null) {
            if(!myDebugSession.isTerminated())
                myDebugSession.terminate();
            if(myDebugSession.isConnected())
                myDebugSession.disconnect();
            myDebugSession = null;
        }
    }

    /***
     *  Close the debug stream.
     */
    private void closeDebugStream() {
        if(myDebugStream != null) {
            try {
                myDebugStream.close();
            } catch (IOException e) {  }
            myDebugStream = null;
        }
    }

    /***
     * Enable debugger access.
     * @throws JSONException
     * @throws IOException
     * @throws ExecutionException
     */
    private void enableDebuggerAccess() throws JSONException, IOException, ExecutionException {
        if(!sendSystemManagerCommand("setJavascriptFlags", "{\"flags\":\"--debugger_agent\"}") ||
           !sendSystemManagerCommand("debugger", "{\"enable\":true}"))
            throw new ExecutionException("Unable to enable V8 debugger");
        sleep(250);
    }

    private void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
        }
    }

    /***
     * Disable debugger access. In case something goes wrong, ignore all exceptions.
     */
    private void disableDebuggerAccess() {
        try {
            sendSystemManagerCommand("debugger", "{\"enable\":false}");
        } catch (Exception e) {
        }
    }

    /***
     * Send the specified command to the systemmanager service.
     * @param command   Command to send
     * @param params    Parameters
     * @return          true if successful
     * @throws JSONException
     * @throws IOException
     */
    private boolean sendSystemManagerCommand(String command, String params) throws JSONException, IOException {
        ServiceManager serviceManager = new ServiceManager(myDeviceConnection);
        JSONInputStream input = serviceManager.lunaSend(new String[]{"-n", "-1"},
                "com.palm.systemmanager",
                command, new JSONObject(params));
        JSONObject result = input.readJSONObject();
        return result.getBoolean("returnValue");
    }

    /***
     * Return the internal device name for a given RunTarget.
     * @param runTarget     RunTarget
     * @return
     * @throws ExecutionException
     */
    private String getTargetDeviceName(RunTarget runTarget) throws ExecutionException {
        switch (runTarget) {
            case EMULATOR:
                return "tcp";
            case DEVICE:
                return "usb";
            default:
                throw new ExecutionException("Invalid target");
        }
    }

    public boolean isReady() {
        return (myDebugSession!= null) && myDebugSession.isConnected() && !myDebugSession.isTerminated();
    }
}
